home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 722 / 722.xpi / chrome / noscript.jar / content / noscript / ClearClickHandler.js < prev    next >
Text File  |  2010-02-12  |  30KB  |  947 lines

  1. function ClearClickHandler(ns) {
  2.   this.ns = ns;
  3.   if (ns.geckoVersionCheck("1.9.2") < 0)
  4.     INCLUDE("ClearClickHandlerLegacy");
  5. }
  6.  
  7. ClearClickHandler.prototype = {
  8.   
  9.   // TODO:
  10.   // 1. try to use MozAfterPaint (Fx 3.1) to intercept "Sudden Reveal" attacks
  11.   // 2. try to use http://www.oxymoronical.com/experiments/apidocs/interface/nsIDOMWindowUtils:compareCanvases
  12.   
  13.   uiEvents: ["mousedown", "mouseup", "click", "dblclick", "keydown", "keypress", "keyup", "blur"],
  14.   install: function(browser) {
  15.     var ceh = browser.docShell.chromeEventHandler;
  16.     var l = this._listener;
  17.     for each(var et in this.uiEvents) ceh.addEventListener(et, this._listener, true);
  18.   },
  19.   
  20.   exceptions: null,
  21.   
  22.   get _listener() {
  23.     var self = this;
  24.     var l = function(ev) { self.handle(ev); };
  25.     this.__defineGetter__("_listener", function() { return l; });
  26.   },
  27.   
  28.   sameSiteParents: function(w) {
  29.     const ns = this.ns;
  30.     var site = ns.getSite(w.location.href);
  31.     if (site == "about:blank") site = "";
  32.     var parentSite;
  33.     for(var p = w.parent; p != w; w = p, p = w.parent) {
  34.       parentSite = ns.getSite(p.location.href);
  35.       if (!site || /^(?:chrome|resource|about):/.test(parentSite)) {
  36.         site = parentSite;
  37.         continue;
  38.       }
  39.       if (site != parentSite) return false;
  40.     }
  41.     if (ns.consoleDump & LOG_CLEARCLICK) ns.dump("ClearClick skipping, same site parents for " + site);
  42.     return true;
  43.   },
  44.   
  45.   appliesHere: function(url) {
  46.     const ns = this.ns;
  47.     return ns.appliesHere(ns.clearClick, url) &&
  48.      !(this.exceptions && this.exceptions.test(url) && ns.isJSEnabled(ns.getSite(url)));
  49.   },
  50.   
  51.   checkSubexception: function(url) {
  52.     return this.subexceptions && this.subexceptions.test(url);
  53.   },
  54.   
  55.   _whitelist: {},
  56.   whitelistLen: 0,
  57.   isWhitelisted: function(w) {
  58.     
  59.     var l = this._whitelist[w.location.href];
  60.     if (!l) return false;
  61.  
  62.     var pp = [];
  63.     for(var p = w.parent; p != w; w = p, p = w.parent) {
  64.       pp.push(p.location.href);
  65.     }
  66.     return l.indexOf(pp.join(" ")) > -1;
  67.   },
  68.   
  69.   whitelist: function(w) {
  70.     if (this.isWhitelisted(w)) return;
  71.     var u = w.location.href;
  72.  
  73.     var pp = [];
  74.     for(var p = w.parent; p != w; w = p, p = w.parent) {
  75.       pp.push(p.location.href);
  76.     }
  77.     
  78.     var l;
  79.     if (u in this._whitelist) l = this._whitelist[u];
  80.     else {
  81.       l = this._whitelist[u] = [];
  82.       this.whitelistLen++;
  83.     }
  84.     
  85.     l.push(pp.join(" "));
  86.   },
  87.   resetWhitelist: function() {
  88.     this._whitelist = {};
  89.     this.whitelistLen = 0;
  90.   },
  91.   
  92.   isEmbed: function(o) {
  93.     return (o instanceof CI.nsIDOMHTMLObjectElement || o instanceof CI.nsIDOMHTMLEmbedElement)
  94.       && !o.contentDocument && ns.isWindowlessObject(o);
  95.   },
  96.   
  97.   swallowEvent: function(ev) {
  98.     ev.cancelBubble = true;
  99.     ev.stopPropagation();
  100.     ev.preventDefault();
  101.   },
  102.   
  103.   _zoom: 1,
  104.   
  105.   
  106.   getBox: function(o, d, w) {
  107.     if (!d) d = o.ownerDocument;
  108.     if (!w) w = d.defaultView;
  109.     var c = o.getBoundingClientRect();
  110.     var x = c.left, y = c.top; // this is relative to the view port, just like mozInnerScreen*
  111.     
  112.     return {
  113.       x: x + w.scrollX, y: y + w.scrollY, // add scroll* to make it absolute
  114.       width: c.width, height: c.height,
  115.       screenX: w.mozInnerScreenX + x, screenY: w.mozInnerScreenY + y
  116.     }
  117.   },
  118.   
  119.   
  120.   getBG: function(w) {
  121.     var bg = w.document.body && w.getComputedStyle(w.document.body, '').backgroundColor || "white";
  122.     return bg == "transparent" ? w != w.parent && this.getBG(w.parent) || "white" : bg;
  123.   },
  124.   
  125.   _constrain: function(box, axys, dim, max, vp, center) {
  126.     var d;
  127.     var scr = "screen" + axys.toUpperCase();
  128.     // trim bounds to take in account fancy overlay borders
  129.     var l = box[dim];
  130.     var n = box[axys];
  131.     
  132.     if (vp.frame && center && l < vp[dim]) { // expand to viewport if possible
  133.       l = vp[dim];
  134.     }
  135.     
  136.     if (l > 6) {
  137.       var bStart = Math.floor(l * .1) // 20% border
  138.       var bEnd = bStart;
  139.       if (bStart + n > center) {
  140.         bStart = center - n;
  141.       } else if (l + n - center < bEnd) {
  142.         bEnd = l + n - center;
  143.       } 
  144.       box[dim] = (l -= (bStart + bEnd));
  145.       box[axys] = (n += bStart);
  146.       box[scr] += bStart;
  147.       
  148.     }
  149.  
  150.     if (l > max) {
  151.       // resize
  152.       if (center) {
  153.         var halfMax = Math.round(max / 2);
  154.         var nn = center - halfMax;
  155.         if (nn > n && center + halfMax > n + l) nn = (n + l) - max;        
  156.         box[axys] = nn;
  157.         box[scr] += (nn - n);
  158.         n = nn;
  159.       }
  160.       l = box[dim] = max;
  161.     }
  162.     // slide into viewport
  163.     var vpn = vp[axys];
  164.     d = (n < vpn)
  165.         ? vpn - n
  166.         : (n + l) > (vpn + vp[dim])
  167.           ? (vpn + vp[dim]) - (n + l)
  168.           : 0;
  169.     
  170.     if (d) {
  171.       n = (box[axys] += d);
  172.       box[scr] += d;
  173.     }
  174.  
  175.   },
  176.   
  177.   createCanvas: function(doc) {
  178.     return doc.__clearClickCanvas || (doc.__clearClickCanvas = doc.createElementNS(HTML_NS, "canvas"));
  179.   },
  180.   
  181.   isSupported: function(doc) {
  182.     return true; 
  183.   },
  184.   
  185.   _semanticContainers: [CI.nsIDOMHTMLParagraphElement, CI.nsIDOMHTMLQuoteElement,
  186.                         CI.nsIDOMHTMLUListElement, CI.nsIDOMHTMLOListElement, CI.nsIDOMHTMLDirectoryElement,
  187.                         CI.nsIDOMHTMLPreElement, CI.nsIDOMHTMLTableElement ]
  188.   ,
  189.   isSemanticContainer: function(o) {
  190.     for each (var t in this._semanticContainers)
  191.      if (o instanceof t) return true;
  192.     return false;
  193.   },
  194.   
  195.   forLog: function(o) {
  196.     return o.tagName + "/" + (o.tabIndex || 0);
  197.   },
  198.   
  199.   handle: function(ev) {
  200.     const o = ev.target;
  201.     const d = o.ownerDocument;
  202.     if (!(d && this.isSupported(d))) return;
  203.     
  204.     const w = d.defaultView;
  205.     if (!w) return;
  206.     
  207.     const top = w.top;
  208.     const ns = this.ns;
  209.     
  210.     var isEmbed;
  211.     
  212.     if (top.__clearClickUnlocked || (top.__clearClickUnlocked = !this.appliesHere(top.location.href)) ||
  213.         o.__clearClickUnlocked ||
  214.         o != ev.originalTarget ||
  215.         o == d.documentElement || o == d.body || // key event on empty region
  216.         this.isSemanticContainer(o) ||
  217.         (o.__clearClickUnlocked = !(isEmbed = this.isEmbed(o)) && // plugin embedding?
  218.             (w == w.top || w.__clearClickUnlocked ||
  219.               (w.__clearClickUnlocked = this.isWhitelisted(w))
  220.               || this.sameSiteParents(w)) || // cross-site document?
  221.         ns.getPluginExtras(o) || // NS placeholder?
  222.         this.checkSubexception(isEmbed && (o.src || o.data) || w.location.href)
  223.         )
  224.       ) return;
  225.     
  226.     var p = ns.getExpando(o, "clearClickProps", {});
  227.     var verbose = ns.consoleDump & LOG_CLEARCLICK;
  228.     var etype = ev.type;
  229.     if (verbose) ns.dump(o.tagName + ", " + etype + ", " + p.toSource());
  230.     
  231.     var ts = 0, obstructed, ctx, primaryEvent;
  232.     try {
  233.       if (etype == "blur") {
  234.         if(/click|key/.test(p.lastEtype)) {
  235.           if (verbose) ns.dump("ClearClick: resetting status on " + this.forLog(o) + " for " + etype);
  236.           if (p.unlocked) p.unlocked = false;
  237.         }
  238.         return;
  239.       }
  240.       if (p.unlocked) return;
  241.     
  242.       ts = Date.now();
  243.       ctx = /mouse/.test(etype)
  244.                 && { x: ev.pageX, y: ev.pageY, debug: ev.ctrlKey && ev.button == 1 && ns.getPref("clearClick.debug") }
  245.                 || {};
  246.       ctx.isEmbed = isEmbed;
  247.       
  248.       primaryEvent = /^(?:mousedown|keydown)$/.test(etype) ||
  249.           // submit button generates a syntethic click if any text-control receives [Enter]: we must consider this "primary"
  250.              etype == "click" && ev.screenX == 0 && ev.screenY == 0 && ev.pageX == 0 && ev.pageY == 0 && ev.clientX == 0 && ev.clientY == 0 && ev.target.form &&
  251.             ((ctx.box = this.getBox(ev.target, d, w)).screenX * ctx.box.screenY != 0) ||
  252.           // allow infra-document drag operations and tabulations
  253.           etype != "blur" && top.__clearClickDoc == d && (top.__clearClickProps.unlocked || top.__clearClickProps.lastEtype == "blur");
  254.     
  255.       obstructed = (primaryEvent || !("obstructed" in p))
  256.         ? p.obstructed = this.checkObstruction(o, ctx)
  257.         : p.obstructed; // cache for non-primary events       
  258.     } catch(e) {
  259.       ns.dump(e.message + ": " + e.stack);
  260.       obstructed = true;
  261.     } finally {
  262.       p.lastEtype = etype;
  263.       top.__clearClickProps = p;
  264.       top.__clearClickDoc = d;
  265.     }
  266.     
  267.     var quarantine = ts - (p.ts || 0);
  268.     
  269.     if (verbose) ns.dump("ClearClick: " + ev.target.tagName + " " + etype +
  270.        "(s:{" + ev.screenX + "," + ev.screenY + "}, p:{" + ev.pageX + "," + ev.pageY + "}, c:{" + ev.clientX + "," + ev.clientY + 
  271.        ", w:" + ev.which + "}) - obstructed: " + obstructed + ", check time: " + (new Date() - ts) + ", quarantine: " + quarantine +
  272.        ", primary: " + primaryEvent + ", ccp:" + (top.__clearClickProps && top.__clearClickProps.toSource()));
  273.     
  274.     var unlocked = !obstructed && primaryEvent && quarantine > 3000;
  275.     
  276.     if (unlocked) {
  277.       if (verbose) ns.dump("ClearClick: unlocking " + ev.target.tagName + " " + etype);
  278.       p.unlocked = true;
  279.     } else {
  280.       
  281.       this.swallowEvent(ev);
  282.       ns.log("[NoScript ClearClick] Swallowed event " + etype + " on " + this.forLog(o) + " at " + w.location.href, true);
  283.       var docShell = DOM.getDocShellForWindow(w);
  284.       var loading = docShell && (docShell instanceof CI.nsIWebProgress) && docShell.isLoadingDocument;
  285.       if (!loading) {
  286.         p.ts = ts;
  287.         if (primaryEvent && ctx.img && ns.getPref("clearClick.prompt") && !this.prompting) {
  288.           try {
  289.             this.prompting = true;
  290.             var params = {
  291.               url: isEmbed && (o.src || o.data) || o.ownerDocument.URL,
  292.               pageURL: w.location.href,
  293.               topURL: w.top.location.href,
  294.               img: ctx.img,
  295.               locked: false,
  296.               pageX: ev.pageX,
  297.               pageY: ev.pageY,
  298.               zoom: this._zoom
  299.             };
  300.             DOM.findBrowserForNode(w).ownerDocument.defaultView.openDialog(
  301.               "chrome://noscript/content/clearClick.xul",
  302.               "noscriptClearClick",
  303.               "chrome, dialog, dependent, centerscreen, modal",
  304.               params);
  305.             if (!params.locked) {
  306.               w.__clearClickUnlocked = o.__clearClickUnlocked = true
  307.               this.whitelist(w);
  308.             }
  309.           } finally {
  310.             this.prompting = false;
  311.           }
  312.         }
  313.       }
  314.     }
  315.   },
  316.   
  317.   findParentForm: function(o) {
  318.     var ftype = CI.nsIDOMHTMLFormElement;
  319.     while((o = o.parentNode)) {
  320.       if (o instanceof ftype) return o;
  321.     }
  322.     return null;
  323.   },
  324.   
  325.  
  326.   rndColor: function() {
  327.     var c = Math.round(Math.random() * 0xffffff).toString(16);
  328.     return "#" + ("000000".substring(c.length)) + c; 
  329.   },
  330.   
  331.    
  332.   maxWidth: 350,
  333.   maxHeight: 200,
  334.   minWidth: 160,
  335.   minHeight: 100,
  336.   _NO_SCROLLBARS: {w: 0, h: 0},
  337.   computeScrollbarSizes: function(frame, dElem, body) {
  338.     var fw = frame.clientWidth, fh = frame.clientHeight;
  339.     var dw = dElem.clientWidth, dh = dElem.clientHeight;
  340.  
  341.     var w = Math.min(fw, dw), h = Math.min(fh, dh);
  342.     if (body) {
  343.       var bw = body.clientWidth;
  344.       var bh = body.clientHeight;
  345.       if (bw <= fw && (bw - dw > 24 || dw > fw)) w = bw; 
  346.      
  347.       if (bh <= fh && (bh - dh > 24 || dh > fh)) h = bh;
  348.  
  349.     }
  350.     
  351.     return { w: fw - w, h: fh - h };
  352.   },
  353.   
  354.   checkObstruction: function(o, ctx) {   
  355.     var d = o.ownerDocument;
  356.     var dElem = d.documentElement;
  357.     
  358.     var w = d.defaultView;
  359.     var top = w.top;
  360.     var browser = DOM.findBrowserForNode(top);
  361.     
  362.     var c = this.createCanvas(browser.ownerDocument);
  363.     var gfx = c.getContext("2d");
  364.     
  365.     var bg = this.getBG(w);
  366.  
  367.  
  368.     var bgStyle;
  369.     var box, curtain;
  370.     
  371.     var frame, frameClass, frameStyle, objClass, viewer;
  372.     
  373.     var docPatcher = new DocPatcher(this.ns, o, w);
  374.     
  375.     var sheet = null;
  376.     
  377.     var img1 = null, img2 = null, tmpImg = null;
  378.     
  379.     function snapshot(w, x, y) {
  380.       gfx.drawWindow(w, Math.round(x), Math.round(y), c.width, c.height, bg);
  381.       return c.toDataURL();
  382.     }
  383.     
  384.     function snapshots(x1, y1, x2, y2) {
  385.       img1 = null;
  386.       try {
  387.         if (objClass) docPatcher.clean(true);
  388.         img1 = snapshot(w, x1, y1);
  389.       } catch(ex) {
  390.         throw ex;
  391.       } finally {
  392.         docPatcher.clean(false);
  393.       }
  394.       img2 = tmpImg = snapshot(top, x2, y2);
  395.       return (img1 != img2); 
  396.     }
  397.     var sd = this._NO_SCROLLBARS;
  398.  
  399.     try {
  400.        
  401.       docPatcher.linkAlertHack(true);
  402.       docPatcher.fbPresenceHack(true);
  403.       
  404.       try {
  405.         docPatcher.opaque(true);
  406.         
  407.         var fbPresence; // hack for Facebooks's fixed positioned widget
  408.         
  409.         if (ctx.isEmbed) { // objects and embeds
  410.           if (this.ns.getPref("clearClick.plugins", true)) {
  411.             var ds = browser.docShell;
  412.             viewer = ds.contentViewer && false;
  413.             objClass = new ClassyObj(o);
  414.             objClass.append(" __noscriptBlank__");
  415.             docPatcher.blankPositioned(true);
  416.             docPatcher.clean(true);
  417.           } else {
  418.             DOM.addClass(o, "__noscriptOpaqued__");
  419.           }
  420.         }
  421.         
  422.         if ((frame = w.frameElement)) {
  423.           frameClass = new ClassyObj(frame);
  424.           DOM.removeClass(frame, "__noscriptScrolling__");
  425.           sd = this.computeScrollbarSizes(frame, dElem, d.body);
  426.           var zoom = d.defaultView.QueryInterface(CI.nsIInterfaceRequestor)
  427.             .getInterface(CI.nsIDOMWindowUtils).screenPixelsPerCSSPixel;
  428.           sd.w *= zoom;
  429.           sd.h *= zoom;
  430.         }
  431.         
  432.         var clientHeight = w.innerHeight - sd.h;
  433.         var clientWidth =  w.innerWidth - sd.w;
  434.         // print(dElem.clientWidth + "," +  dElem.clientHeight + " - "  + w.innerWidth + "," + w.innerHeight);
  435.         
  436.         if (!ctx.isEmbed) {
  437.           curtain = d.createElementNS(HTML_NS, "div");
  438.           with (curtain.style) {
  439.             top = left = "0px";
  440.             
  441.             width = (clientWidth + w.scrollX) + "px";
  442.             height = (clientHeight + w.scrollY) + "px";
  443.   
  444.             padding = margin = borderWidth = MozOutlineWidth = "0px";
  445.             position = "absolute";
  446.             zIndex = "99999999";
  447.             
  448.             background = this.rndColor();
  449.           }
  450.           frameStyle = w.parent.getComputedStyle(frame, '');
  451.         }     
  452.         
  453.         if (curtain && frame) {
  454.           dElem.appendChild(curtain);
  455.         }
  456.         
  457.         var maxWidth = Math.max(Math.min(this.maxWidth, clientWidth), this.minWidth);
  458.         var maxHeight = Math.max(Math.min(this.maxHeight, clientHeight), this.minHeight);
  459.   
  460.         box = this.getBox(o, d, w);
  461.         
  462.         // expand to parent form if needed
  463.         var form = o.form;
  464.         var formBox = null;
  465.         if (frame && !ctx.isEmbed && (form || (form = this.findParentForm(o)))) {
  466.   
  467.           formBox = this.getBox(form, d, w);
  468.           if (!(formBox.width && formBox.height)) { // some idiots put <form> as first child of <table> :(
  469.             formBox = this.getBox(form.offsetParent || form.parentNode, d, w);
  470.             if (!(formBox.width && formBox.height)) {
  471.               formBox = this.getBox(form.parentNode.offsetParent || o.offsetParent, d, w);
  472.             }
  473.           }
  474.     
  475.           if (formBox.width && formBox.height) {
  476.             ctx.x = ctx.x || box.x + box.width;
  477.             ctx.y = ctx.y || box.y + box.height;
  478.             box = formBox;
  479.             var delta;
  480.             if (box.x < 0) {
  481.               box.screenX -= box.x;
  482.               box.x = 0;
  483.             }
  484.             if (box.y < 0) {
  485.               box.screenY -= box.y;
  486.               box.y = 0;
  487.             }
  488.             if (box.x + Math.min(box.width, maxWidth) < ctx.x) {
  489.               box.width = Math.min(box.width, maxWidth);
  490.               delta = ctx.x + 4 - box.width - box.x;
  491.               box.x += delta;
  492.               box.screenX += delta;
  493.              
  494.             }
  495.             if (box.y + Math.min(box.height, maxHeight) < ctx.y) {
  496.               box.height = Math.min(box.height, maxHeight);
  497.               delta = ctx.y + 4 - box.height - box.y;
  498.               box.y += delta;
  499.               box.screenY += delta;
  500.             }
  501.             o = form;
  502.           }
  503.         }
  504.   
  505.         bgStyle = dElem.style.background;
  506.         dElem.style.background = bg;
  507.         
  508.         // clip, slide in viewport and trim
  509.         
  510.         var vp = { 
  511.           x: w.scrollX, 
  512.           y: w.scrollY, 
  513.           width: Math.max(w.innerWidth - sd.w, 32), 
  514.           height: Math.max(w.innerHeight - sd.h, 32),
  515.           frame: frame
  516.         };
  517.  
  518.         if (ctx.isEmbed) { // check in-page vieport
  519.           vp.frame = null;
  520.           vp.x = Math.max(vp.x, box.x);
  521.           vp.y = Math.max(vp.y, box.y);
  522.           vp.width = Math.min(vp.width, box.width);
  523.           vp.height = Math.min(vp.height, box.height);
  524.           
  525.           for(form = o; form = form.parentNode;) {
  526.   
  527.             if ((form.offsetWidth < box.width || form.offsetHeight < box.height) &&
  528.                 w.getComputedStyle(form, '').overflow != "visible") {
  529.               
  530.               // check if we're being fooled by some super-zoomed applet
  531.               if (box.width / 4 <= form.offsetWidth && box.height / 4 <= form.offsetHeight) {
  532.                 formBox = this.getBox(form, d, w);
  533.                 
  534.                 if (box.x < formBox.x) {
  535.                   box.x = formBox.x;
  536.                   box.screenX = formBox.screenX;
  537.                 }
  538.                 if (box.y < formBox.y) { 
  539.                   box.y = formBox.y;
  540.                   box.screenY = formBox.screenY;
  541.                 }
  542.                 if (box.width + box.x > formBox.width + formBox.x) box.width = Math.max(this.minWidth, form.clientWidth - (box.x - formBox.x));
  543.                 if (box.height + box.y > formBox.height + formBox.y) box.height = Math.max(this.minHeight, form.offsetHeight - (box.y - formBox.y));
  544.               }
  545.               break;
  546.             }
  547.           }
  548.         } else if (!(sd.w || sd.h)) { // no scrollbars
  549.           if (!sd.w) {
  550.             vp.x = 0;
  551.             vp.width = curtain.offsetWidth;
  552.           }
  553.           if (!sd.h) {
  554.             vp.y = 0;
  555.             vp.height = curtain.offsetHeight;
  556.           }
  557.         }
  558.         
  559.         box.oX = box.x;
  560.         box.oY = box.y;
  561.         box.oW = box.width;
  562.         box.oH = box.height;
  563.         
  564.         // print("Fitting " + box.toSource() + " in " + vp.toSource() + " - ctx " + ctx.x + ", " + ctx.y + " - max " + maxWidth + ", " + maxHeight);
  565.   
  566.         this._constrain(box, "x", "width", maxWidth, vp, ctx.x);
  567.         this._constrain(box, "y", "height", maxHeight, vp, ctx.y);
  568.         // print(box.toSource());     
  569.         
  570.         c.width = box.width;
  571.         c.height = box.height;
  572.         
  573.         
  574.         if (this.ns.consoleDump & LOG_CLEARCLICK) this.ns.dump("Snapshot at " + box.toSource() + " + " + w.pageXOffset + ", " + w.pageYOffset);
  575.         
  576.         
  577.         
  578.         img1 = snapshot(w, box.x, box.y);
  579.       
  580.       } finally {
  581.         docPatcher.clean(false);
  582.       }
  583.     
  584.  
  585.       var rootElement = top.document.documentElement;
  586.       var rootBox = this.getBox(rootElement, top.document, top);
  587.       
  588.       var offsetX = (box.screenX - rootBox.screenX);
  589.       var offsetY = (box.screenY - rootBox.screenY);
  590.       var ret = true;
  591.       var tmpImg;
  592.       
  593.       const offs = ctx.isEmbed ? [0] : [0, -1, 1, -2, 2, -3, -3];
  594.  
  595.       checkImage:
  596.       for each(var x in offs) {
  597.         for each(var y in offs) {
  598.           tmpImg = snapshot(top, offsetX + x, offsetY + y);
  599.           if (img1 == tmpImg) {
  600.             ret = false;
  601.             break checkImage;
  602.           }
  603.           if (!img2) img2 = tmpImg;
  604.         }
  605.       }
  606.       
  607.       if (ret && !curtain && ctx.isEmbed) {
  608.         curtain = d.createElementNS(HTML_NS, "div");
  609.         if (docPatcher) curtain.className = docPatcher.shownCS;
  610.         with (curtain.style) {
  611.           // we expand by 1 pixel in order to avoid antialias effects on the edge at zoom != 1 (GMail Flash attachment)
  612.           top = (o.offsetTop - 1) + "px";
  613.           left = (o.offsetLeft -1) + "px";
  614.           width = (o.offsetWidth +2) + "px";
  615.           height = (o.offsetHeight +2) + "px";
  616.           position = "absolute";
  617.           zIndex = w.getComputedStyle(o, '').zIndex;
  618.           background = this.rndColor();
  619.         }
  620.         
  621.         if (o.nextSibling) {
  622.           o.parentNode.insertBefore(curtain, o.nextSibling);
  623.         } else {
  624.           o.parentNode.appendChild(curtain);
  625.         }
  626.         
  627.         ret = snapshots(box.x, box.y, offsetX, offsetY);
  628.       }
  629.       
  630.       if (ret && ctx.isEmbed && ("x" in ctx) && c.width > this.minWidth && c.height > this.minHeight) {
  631.         c.width = this.minWidth;
  632.         c.height = this.minHeight;
  633.         for each(x in [Math.max(ctx.x - this.minWidth, box.oX), Math.min(ctx.x, box.oX + box.oW - this.minWidth)]) {
  634.           for each(y in [Math.max(ctx.y - this.minHeight, box.oY), Math.min(ctx.y, box.oY + box.oH - this.minHeight)]) {
  635.             ret = snapshots(x, y, offsetX + (x - box.x), offsetY + (y - box.y));
  636.             if (!ret) {
  637.               offsetX += (x - box.x);
  638.               offsetY += (y - box.y);
  639.               box.x = x;
  640.               box.y = y;
  641.               break;
  642.             }
  643.           }
  644.           if (!ret) break;
  645.         }
  646.       }
  647.       
  648.       if (ctx.debug) {
  649.         ret = true;
  650.         img2 = tmpImg;
  651.       }
  652.       
  653.       if (ret) {
  654.         
  655.         if (curtain) {
  656.  
  657.           if (ctx.debug) {
  658.             
  659.             if (docPatcher.cleanSheet) {
  660.               curtain.id = "curtain_" + DOM.rndId();
  661.               docPatcher.cleanSheet += " #" + curtain.id + " { opacity: .4 !important }";
  662.             }
  663.             
  664.             curtain.style.opacity = ".4"
  665.             
  666.           } else {
  667.             curtain.parentNode.removeChild(curtain);
  668.           }
  669.           snapshots(box.x, box.y, offsetX, offsetY);
  670.         }
  671.         
  672.         ctx.img =
  673.         {
  674.           src: img1,
  675.           altSrc: img2,
  676.           width: c.width,
  677.           height: c.height
  678.         }
  679.       }
  680.     
  681.     } finally {
  682.       if (ctx.isEmbed) docPatcher.blankPositioned(false);
  683.       
  684.       
  685.       if (curtain && curtain.parentNode) curtain.parentNode.removeChild(curtain);
  686.       if (typeof(bgStyle) == "string") dElem.style.background = bgStyle;
  687.      
  688.       docPatcher.opaque(false);
  689.       docPatcher.linkAlertHack(false);
  690.       docPatcher.fbPresenceHack(false);
  691.       
  692.       if (objClass) objClass.reset();
  693.       if (frameClass) frameClass.reset();
  694.       if (viewer) viewer.enableRendering = true;
  695.     }
  696.     
  697.     return ret;
  698.  
  699.   }
  700.   
  701. }
  702.  
  703. function ClassyObj(o) {
  704.   this.o = o;
  705.   if (o.hasAttribute("class")) this.c = o.className;
  706. }
  707. ClassyObj.prototype = {
  708.   o: null,
  709.   c: null,
  710.   append: function(newC) {
  711.     try {
  712.       this.o.className = this.c ? this.c + newC : newC;
  713.     } catch(e) {}
  714.   },
  715.   reset: function() {
  716.     try {
  717.       if (this.c == null) this.o.removeAttribute("class");
  718.       else this.o.className = this.c;
  719.     } catch(e) {}
  720.   }
  721. }
  722.  
  723. function DocPatcher(ns, o, w) {
  724.   this.ns = ns;
  725.   this.o = o;
  726.   this.win = w;
  727.   this.top = w.top;
  728.   this.shownCS = " __noscriptShown__" + DOM.rndId();
  729. }
  730.  
  731. DocPatcher.prototype = {
  732.  
  733.   collectAncestors: function(o) {
  734.     var res = [];
  735.     for(; o && o.hasAttribute; o = o.parentNode) res.push(new ClassyObj(o));
  736.     return res;
  737.   },
  738.   
  739.   getRect: function(o, d) {
  740.     return (this.getRect = ("getBoundingClientRect" in o)
  741.      ? function(o) { return o.getBoundingClientRect() }
  742.      : function(o, d) {
  743.       var b = d.getBoxObjectFor(o);
  744.       var x = o.x, y = o.y;
  745.       return {
  746.         left: x, top: y,
  747.         right: x + o.width, left: y + o.height 
  748.       };
  749.      })(o, d)
  750.   },
  751.   
  752.   collectPositioned: function(d) {
  753.     var t = Date.now();
  754.     const w = d.defaultView;
  755.     const res = [];
  756.     var s = null, p = '', n = null;
  757.  
  758.     const r = this.getRect(this.o, d);
  759.     const top = r.top;
  760.     const bottom = r.bottom;
  761.     const left = r.left;
  762.     const right = r.right;
  763.     
  764.     var c = '', b = null;
  765.     var hasPos = false;
  766.     const posn = [];
  767.     
  768.     const tw = d.createTreeWalker(d, CI.nsIDOMNodeFilter.SHOW_ELEMENT, null, false);
  769.     for (var n = null; (n = tw.nextNode());) {
  770.       b = this.getRect(n, d);
  771.       if (b.bottom < top || b.top > bottom ||
  772.           b.right < left || b.left > right)
  773.         continue;
  774.       
  775.       s = w.getComputedStyle(n, '');
  776.       p = s.position;
  777.       if (p == "absolute" || p == "fixed") {
  778.         c = " __noscriptPositioned__";
  779.         n.__noscriptPos = hasPos = true;
  780.         posn.push(n);
  781.       } else { 
  782.         hasPos = hasPos && n.parentNode.__noscriptPos;
  783.         if (!hasPos) continue;
  784.         c = '';
  785.         n.__noscriptPos = true;
  786.         posn.push(n);
  787.       }
  788.       
  789.       if (s.backgroundImage != "none" || s.backgroundColor != "transparent") {
  790.         c += " __noscriptBlank__";
  791.       };
  792.       
  793.       
  794.       if (c) {
  795.         res.push(n = new ClassyObj(n));
  796.         n.append(c);
  797.       }1
  798.     }
  799.     
  800.     for each(n in posn) n.__noscriptPos = false;
  801.     
  802.     if(ns.consoleDump & LOG_CLEARCLICK) this.ns.dump("DocPatcher.collectPositioned(): " + (Date.now() - t));
  803.     return res;
  804.   },
  805.   
  806.   collectOpaqued: function(o, oo) {
  807.     if (!oo) oo = { opacity: 1, res: [] };
  808.     
  809.     var w = o.ownerDocument.defaultView;
  810.     
  811.     var opacity;
  812.     var co = null;
  813.     for(; o && o.hasAttribute; o = o.parentNode) {
  814.       opacity = parseFloat(w.getComputedStyle(o, '').opacity);
  815.       if (opacity < 1) {
  816.         if ((oo.opacity *= opacity) < .3) return []; // too much combined transparency!
  817.         oo.res.push(new ClassyObj(o));
  818.       }
  819.     }
  820.        
  821.     o = w.frameElement;
  822.     return o ? this.collectOpaqued(o, oo) : oo.res;
  823.   },
  824.   
  825.   forceVisible: function(co) { // TODO: I cause too much reflow, please CHECK ME!
  826.     co.append(this.shownCS); 
  827.   },
  828.   
  829.   forceOpaque: function(co) {
  830.     co.append(" __noscriptJustOpaqued__");
  831.   },
  832.   
  833.   resetClass: function(co) {
  834.     co.reset();
  835.   },
  836.   
  837.   _ancestors: null,
  838.   _cleanSheetHandle: null,
  839.   clean: function(toggle) {
  840.     if (toggle) {
  841.       if (!this._ancestors) {
  842.         this.cleanSheet = "body * { visibility: hidden !important } body ." + this.shownCS.substring(1) + " { visibility: visible !important; opacity: 1 !important }";
  843.         this._ancestors = this.collectAncestors(this.o);
  844.       }
  845.       this._ancestors.forEach(this.forceVisible, this);
  846.       this._cleanSheetHandle = this.applySheet(this.cleanSheet);
  847.     } else if (this._ancestors) {
  848.       this._ancestors.forEach(this.resetClass);
  849.       this.removeSheet(this._cleanSheetHandle);
  850.     }
  851.   },
  852.   
  853.   _positioned: null,
  854.   _blankSheetHandle: null,
  855.   blankSheet: ".__noscriptPositioned__ * { color: white !important; border-color: white !important; }",
  856.   blankPositioned: function(toggle) {
  857.     if (toggle) {
  858.       this._positioned = this.collectPositioned(this.o.ownerDocument);
  859.       this._blankSheetHandle = this.applySheet(this.blankSheet);
  860.     } else if (this._positionased) {
  861.       this._positioned.forEach(this.resetClass);
  862.       this.removeSheet(this._blankSheetHandle);
  863.     }
  864.   },
  865.   
  866.   _opaqued: null,
  867.   opaque: function(toggle) {
  868.     if (toggle) {
  869.       this._opaqued = this._opaqued || this.collectOpaqued(this.o);
  870.       this._opaqued.forEach(this.forceOpaque);
  871.     } else if (this._opaqued) {
  872.       this._opaqued.forEach(this.resetClass);
  873.     }
  874.   },
  875.   
  876.   get oldStyle() {
  877.     delete this.__proto__.oldStyle;
  878.     return this.__proto__.oldStyle = Components.ID('{41d979dc-ea03-4235-86ff-1e3c090c5630}')
  879.                  .equals(CI.nsIStyleSheetService);
  880.   },
  881.   applySheet: function(sheet) {
  882.     var sheetHandle = null;
  883.     if (this.oldStyle) {
  884.       // Gecko < 1.9, asynchronous user sheets force ugly work-around
  885.       var d = this.o.ownerDocument;
  886.       sheetHandle = d.createElementNS(HTML_NS, "style");
  887.       sheetHandle.innerHTML = sheet;
  888.       d.documentElement.appendChild(sheetHandle);
  889.     } else { // 
  890.       this.ns.updateStyleSheet(sheetHandle = sheet, true);
  891.     }
  892.     return sheetHandle;
  893.   },
  894.   removeSheet: function(sheetHandle) {
  895.     if (sheetHandle) {
  896.       if (this.oldStyle) {
  897.         this.o.ownerDocument.documentElement.removeChild(sheetHandle);
  898.       } else {
  899.         this.ns.updateStyleSheet(sheetHandle, false);
  900.       }
  901.     }
  902.   },
  903.   
  904.   _linkAlertBox: null,
  905.   linkAlertHack: function(toggle) {
  906.     try {
  907.       var w = this.top;
  908.       var d = w.document;
  909.       if (toggle) {
  910.         var box = d.getElementById("linkalert-box");
  911.         if (!box || box.style.display) return;
  912.         var imgs = box.getElementsByTagName("img");
  913.         if (imgs.length > 5) return;
  914.         var img;
  915.         for (var j = imgs.length; j-- > 0;) {
  916.           img = imgs[j];
  917.           if (!/^(?:chrome:\/\/linkalert\/skin\/|(?:moz\-icon|file):\/\/)/.test(img.src) || img.naturalWidth == 0 ||
  918.             img.offsetWidth > 32 || img.offsetHeight > 32) return;
  919.         }
  920.         box.style.display = "none";
  921.         this._linkAlertBox = box;
  922.       } else {
  923.         if (this._linkAlertBox) {
  924.           this._linkAlertBox.style.display = "";
  925.           this._linkAlertBox = null;
  926.         }
  927.       }
  928.     } catch (e) {}
  929.   },
  930.   
  931.   _fbPresence: null,
  932.   fbPresenceHack: function(toggle) {
  933.     if (toggle) {
  934.       if (this.top.location.host == "apps.facebook.com") {
  935.         var fbPresence = this.top.document.getElementById("presence");
  936.         if (fbPresence) {
  937.           fbPresence._ccVisibility = fbPresence.style.visibility;
  938.           fbPresence.style.visibility = "hidden";
  939.           this._fbPresence = fbPresence; 
  940.         }
  941.       }
  942.     } else if (this._fbPresence) {
  943.       this._fbPresence.style.visibility = this._fbPresence._ccVisibility;
  944.     }
  945.   }
  946.   
  947. }